using System;
using System.Collections.Generic;
using System.Text;

namespace PokerLearner.Poker
{
	public enum HandType
	{
		HighCard,
		Pair,
		TwoPair,
		ThreeOfAKind,
		Straight,
		Flush,
		FullHouse, 
		FourOfAKind,
		StraightFlush,
	}

	[Serializable]
	public class Hand: IComparable<Hand>
	{
		#region Value

		private int value;
		public int Value
		{
			get { return value; }
		}

		#endregion

		#region Type

		private HandType type;
		public HandType Type
		{
			get { return type; }
			private set { type = value; }
		}

		#endregion

		#region Cards

		private Card[] cards;

		public Card Card1
		{
			get { return cards[0]; }
			private set { cards[0] = value; }
		}

		public Card Card2
		{
			get { return cards[1]; }
			private set { cards[1] = value; }
		}

		public Card Card3
		{
			get { return cards[2]; }
			private set { cards[2] = value; }
		}

		public Card Card4
		{
			get { return cards[3]; }
			private set { cards[3] = value; }
		}

		public Card Card5
		{
			get { return cards[4]; }
			private set { cards[4] = value; }
		}

		#endregion

		#region Constructor

		private Hand() 
		{
			cards = new Card[5];
			type = HandType.HighCard;
		}

		#endregion

		#region Static Create Hand Methods

		public static Hand CreateBestHand(Card[] pocket, Card[] commonCards)
		{
			// Create every possible hand
			Hand[] hands = new Hand[]
			{
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[1], commonCards[2]),
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[1], commonCards[3]),
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[1], commonCards[4]),
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[2], commonCards[3]),
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[2], commonCards[4]),
				CreateHand(pocket[0], pocket[1], commonCards[0], commonCards[3], commonCards[4]),
				CreateHand(pocket[0], pocket[1], commonCards[1], commonCards[2], commonCards[3]),
				CreateHand(pocket[0], pocket[1], commonCards[1], commonCards[2], commonCards[4]),
				CreateHand(pocket[0], pocket[1], commonCards[1], commonCards[3], commonCards[4]),
				CreateHand(pocket[0], pocket[1], commonCards[2], commonCards[3], commonCards[4]),
				CreateHand(pocket[0], commonCards[0], commonCards[1], commonCards[2], commonCards[3]),
				CreateHand(pocket[0], commonCards[0], commonCards[1], commonCards[2], commonCards[4]),
				CreateHand(pocket[0], commonCards[0], commonCards[1], commonCards[3], commonCards[4]),
				CreateHand(pocket[0], commonCards[0], commonCards[2], commonCards[3], commonCards[4]),
				CreateHand(pocket[0], commonCards[1], commonCards[2], commonCards[3], commonCards[4]),
				CreateHand(pocket[1], commonCards[0], commonCards[1], commonCards[2], commonCards[3]),
				CreateHand(pocket[1], commonCards[0], commonCards[1], commonCards[2], commonCards[4]),
				CreateHand(pocket[1], commonCards[0], commonCards[1], commonCards[3], commonCards[4]),
				CreateHand(pocket[1], commonCards[0], commonCards[2], commonCards[3], commonCards[4]),
				CreateHand(pocket[1], commonCards[1], commonCards[2], commonCards[3], commonCards[4]),
				CreateHand(commonCards[0], commonCards[1], commonCards[2], commonCards[3], commonCards[4]),
			};

			Hand ret = hands[0];
			foreach (Hand hand in hands)
			{
				if (hand.CompareTo(ret) > 0)
					ret = hand;
			}
			return ret;
		}

		public static Hand CreateHand(Card card1, Card card2, Card card3, Card card4, Card card5)
		{
			Hand ret = new Hand();
			ret.Card1 = card1;
			ret.Card2 = card2;
			ret.Card3 = card3;
			ret.Card4 = card4;
			ret.Card5 = card5;

			// Sort the cards
			for (int i = 0; i < 5 && ret.cards[i] != null; i++)
			{
				int smallest = i;
				for (int j = i + 1; j < 5 && ret.cards[j] != null; j++)
				{
					if (ret.cards[j].CompareTo(ret.cards[smallest]) < 0)
					{
						smallest = j;
					}
				}
				if (smallest != i)
				{
					Card help = ret.cards[smallest];
					ret.cards[smallest] = ret.cards[i];
					ret.cards[i] = help;
				}
			}

			bool useAceAsOne = false;
			bool sequential = true;
			for (int i = 1; i < 5 && sequential; i++)
			{
				if (ret.cards[i] == null)
					sequential = false;
				else if (ret.cards[i].Count != ret.cards[0].Count + i)
				{
					if (ret.cards[i].Count == 13 && ret.cards[0].Count == 1)
						useAceAsOne = true;
					else
						sequential = false;
				}

			}

			bool suited = true;
			for (int i = 1; i < 5 && suited; i++)
			{
				if (ret.cards[i] == null)
					suited = false;
				else if (ret.cards[i].Suite != ret.cards[0].Suite)
					suited = false;
			}

			// If all cards are used by as either flush or straight, copy all
			// the card values into the hand value
			if (sequential || suited)
			{				
				for (int i = 0; i < 5 && ret.cards[i] != null; i++)
				{
					// If the ace is part of a low straight then jus skip it
					if (useAceAsOne && ret.cards[i].Count == 13)
						continue;
					
					ret.value |= ret.cards[i].Count << (4 * i);
				}
			}

			if (sequential && suited)
			{
				ret.Type = HandType.StraightFlush;
			}
			else if (sequential)
			{
				ret.Type = HandType.Straight;
			}
			else if (suited)
			{
				ret.Type = HandType.Flush;
			}
			else
			{
				int[,] groups = new int[,] { { -1, 0 }, { -1, 0 } };

				int group = 0;

				int num = 0;
				for (int i = 0; i < 5 && ret.cards[i] != null && group < 2; i += num)
				{
					groups[group, 0] = i;
					num = 1;

					for (int j = i + 1; j < 5 && ret.cards[j] != null; j++)
					{
						if (ret.cards[j].Count == ret.cards[groups[group, 0]].Count)
							num++;
					}

					if (num > 1)
					{
						groups[group, 1] = num;
						group++;
					}
					else
						groups[group, 0] = -1;
				}

				int groupMajor;
				int groupMinor;
				if (groups[0, 1] >= groups[1, 1] || ret.cards[groups[0, 0]].CompareTo(ret.cards[groups[1, 0]]) > 0)
				{
					groupMajor = 0;
					groupMinor = 1;
				}
				else
				{
					groupMajor = 1;
					groupMinor = 0;
				}

				// Find value
				if (groups[0, 1] > 0)
				{
					for (int i = 0; i < groups[groupMajor, 1] && ret.cards[groups[groupMajor, 0] + i] != null; i++)
					{
						ret.value |= ret.cards[groups[groupMajor, 0] + i].Count << (4 * (i + (5 - groups[groupMajor, 1])));
					}
					if (groups[groupMinor, 1] > 0)
					{
						for (int i = 0; i < groups[groupMinor, 1] && ret.cards[groups[groupMinor, 0] + i] != null; i++)
						{
							ret.value |= ret.cards[groups[groupMinor, 0] + i].Count << (4 * (i + (5 - groups[groupMinor, 1] - groups[groupMajor, 1])));
						}
					}					
				}

				int pos = 0;
				for (int i = 0; i < 5 && ret.cards[i] != null; i++)
				{
					if ((i >= groups[groupMajor, 0] && i < groups[groupMajor, 0] + groups[groupMajor, 1]) ||
						(i >= groups[groupMinor, 0] && i < groups[groupMinor, 0] + groups[groupMajor, 1]))
						continue;
					else
						ret.value |= ret.cards[i].Count << (4 * pos++);
				}				

				// Four of a kind
				if (groups[0, 1] == 4)
				{
					ret.Type = HandType.FourOfAKind;
				}
				else if (groups[0, 1] == 3 && groups[1, 1] == 2)
				{
					ret.Type = HandType.FullHouse;
				}
				else if (groups[0, 1] == 2 && groups[1, 1] == 3)
				{
					ret.Type = HandType.FullHouse;
				}
				else if (groups[0, 1] == 3)
				{
					ret.Type = HandType.ThreeOfAKind;
				}
				else if (groups[0, 1] == 2 && groups[1, 1] == 2)
				{
					ret.Type = HandType.TwoPair;
				}
				else if (groups[0, 1] == 2)
				{
					ret.Type = HandType.Pair;
				}
			}

			// Finally the hand type 
			ret.value |= (int)ret.Type << 20;

			return ret;
		}

		#endregion

		#region IComparable<Hand> Members

		public int CompareTo(Hand other)
		{
			if (this.value > other.value)
				return 1;
			else if(this.value < other.value)
				return -1;
			else 
				return 0;
		}

		#endregion

		#region Object Overrides

		public override string ToString()
		{
			return ToString(true);
		}

		public string ToString(bool abbreviate)
		{
			StringBuilder builder = new StringBuilder();
			builder.Append(Type.ToString() + ": ");
			for (int i = 0; i < cards.Length; i++)
			{
				Card card = cards[i];
				if (card != null)
					builder.Append(card.ToString(abbreviate) + ((i != cards.Length - 1) ? ", " : ""));
			}
			return builder.ToString();
		}

		#endregion
	}
}
